package com.lifeonwalden.fincalc.finance.service;

import com.lifeonwalden.fincalc.bean.DateValuePair;
import com.lifeonwalden.fincalc.finance.bean.ReturnRateInfo;
import com.lifeonwalden.fincalc.finance.bean.RollingReturnRateInfo;
import com.lifeonwalden.fincalc.utils.date.DaysUtils;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 收益计算
 */
public interface YieldCalculator {

    /**
     * 计算区间的几何平均收益率
     *
     * @param dataList     区间收益率
     * @param valueFetcher
     * @param <T>
     * @return
     */
    static <T> BigDecimal calcGeometricMean(List<T> dataList, Function<T, BigDecimal> valueFetcher) {
        SummaryStatistics summaryStatistics = new SummaryStatistics();
        dataList.forEach(data -> summaryStatistics.addValue(BigDecimal.ONE.add(valueFetcher.apply(data)).doubleValue()));

        return BigDecimal.valueOf(summaryStatistics.getGeometricMean()).subtract(BigDecimal.ONE).setScale(4, RoundingMode.HALF_DOWN);
    }

    /**
     * 计算区间的几何平均收益率的年/月/周化收益率
     *
     * @param dataList               区间收益率
     * @param valueFetcher
     * @param standardIntervalNumber 标准期数。如年可为：250, 365；月则为：30, 31等； 周则为：5, 7
     * @param <T>
     * @return
     */
    static <T> BigDecimal calcGeometricMeanPeriodization(List<T> dataList, Function<T, BigDecimal> valueFetcher, int standardIntervalNumber) {
        BigDecimal product = BigDecimal.ONE;
        for (T data : dataList) {
            product = product.multiply(BigDecimal.ONE.add(valueFetcher.apply(data)));
        }

        return BigDecimal.valueOf(Math.pow(product.doubleValue(),
                BigDecimal.valueOf(standardIntervalNumber).divide(BigDecimal.valueOf(dataList.size()), 12, RoundingMode.HALF_DOWN).doubleValue()))
                .subtract(BigDecimal.ONE)
                .setScale(4, RoundingMode.HALF_DOWN);
    }

    /**
     * 计算区间收益率
     *
     * @param initValue  初始值
     * @param finalValue 终值
     * @return
     */
    static BigDecimal calcReturnRate(BigDecimal initValue, BigDecimal finalValue) {
        return finalValue.divide(initValue, 4, RoundingMode.HALF_UP).subtract(BigDecimal.ONE);
    }

    /**
     * 计算区间及其年化收益率
     *
     * @param initValue  初始值
     * @param finalValue 终值
     * @return
     */
    static ReturnRateInfo calcAnnualReturnRate(DateValuePair initValue, DateValuePair finalValue) {
        ReturnRateInfo returnRateInfo = new ReturnRateInfo();
        returnRateInfo.setRate(calcReturnRate(initValue.getValue(), finalValue.getValue()));
        if (returnRateInfo.getRate().compareTo(BigDecimal.ZERO) == 0) {
            returnRateInfo.setAnnualRate(BigDecimal.ZERO);
        } else {
            returnRateInfo.setAnnualRate(
                    BigDecimal.valueOf(
                            Math.pow(
                                    1 + returnRateInfo.getRate().doubleValue(),
                                    BigDecimal.valueOf(365)
                                            .divide(BigDecimal.valueOf(DaysUtils.calcWithoutHeadOrTail(initValue.getDate(), finalValue.getDate())), 6, RoundingMode.HALF_UP)
                                            .doubleValue()
                            )
                    ).setScale(4, RoundingMode.HALF_UP).subtract(BigDecimal.ONE)
            );
        }
        return returnRateInfo;
    }


    /**
     * 计算区间及其年化收益率
     *
     * @param initValue  初始值
     * @param finalValue 终值
     * @param convertor
     * @param <T>
     * @return
     */
    static <T> ReturnRateInfo calcAnnualReturnRate(T initValue, T finalValue, Function<T, DateValuePair> convertor) {
        return calcAnnualReturnRate(convertor.apply(initValue), convertor.apply(finalValue));
    }

    /**
     * 计算区间收益率及其收益率曲线
     *
     * @param dataList  日期升序的净值数据
     * @param convertor
     * @param <T>
     * @return
     */
    static <T> ReturnRateInfo calcReturnRateSeries(List<T> dataList, Function<T, DateValuePair> convertor) {
        ReturnRateInfo returnRateInfo = new ReturnRateInfo();
        returnRateInfo.setRateSeries(new ArrayList<>());

        DateValuePair startPoint = convertor.apply(dataList.get(0));
        for (T data : dataList) {
            DateValuePair point = convertor.apply(data);
            returnRateInfo.getRateSeries().add(new DateValuePair().setDate(point.getDate()).setValue(calcReturnRate(startPoint.getValue(), point.getValue())));
        }

        returnRateInfo.setRate(returnRateInfo.getRateSeries().get(dataList.size() - 1).getValue());

        return returnRateInfo;
    }

    /**
     * 计算区间及年化收益率以及收益和年化收益率曲线
     *
     * @param dataList  日期升序的净值数据
     * @param convertor
     * @param <T>
     * @return
     */
    static <T> ReturnRateInfo calcAnnualReturnRateSeries(List<T> dataList, Function<T, DateValuePair> convertor) {
        ReturnRateInfo returnRateInfo = new ReturnRateInfo();
        returnRateInfo.setRateSeries(new ArrayList<>()).setAnnualRateSeries(new ArrayList<>());

        DateValuePair startPoint = convertor.apply(dataList.get(0));
        for (T data : dataList) {
            DateValuePair point = convertor.apply(data);
            ReturnRateInfo pointReturnRate = calcAnnualReturnRate(startPoint, point);
            returnRateInfo.getRateSeries().add(new DateValuePair().setDate(point.getDate()).setValue(pointReturnRate.getRate()));
            returnRateInfo.getAnnualRateSeries().add(new DateValuePair().setDate(point.getDate()).setValue(pointReturnRate.getAnnualRate()));
        }

        returnRateInfo.setRate(returnRateInfo.getRateSeries().get(dataList.size() - 1).getValue())
                .setAnnualRate(returnRateInfo.getAnnualRateSeries().get(dataList.size() - 1).getValue());

        return returnRateInfo;
    }

    /**
     * 计算滚动区间段收益率信息
     *
     * @param dataList          日期升序的净值数据
     * @param convertor
     * @param rollingPeriodDays 滚动区间的天数
     * @param <T>
     * @return
     */
    static <T> Optional<RollingReturnRateInfo> calcRollingReturnRateInfo(List<T> dataList, Function<T, DateValuePair> convertor, int rollingPeriodDays) {
        int size = dataList.size();
        if (size < rollingPeriodDays) {
            return Optional.empty();
        }

        RollingReturnRateInfo rollingReturnRateInfo = new RollingReturnRateInfo();
        rollingReturnRateInfo.setMaxRateIdx(-1).setMinRateIdx(-1);

        DateValuePair[] rateSeries = new DateValuePair[size - rollingPeriodDays + 1];

        for (int i = size - 1; i >= 0; i--) {
            int initIdx = i - rollingPeriodDays + 1;
            if (initIdx < 0) {
                break;
            }
            DateValuePair initPoint = convertor.apply(dataList.get(initIdx));
            DateValuePair finalPoint = convertor.apply(dataList.get(i));

            DateValuePair returnRate = new DateValuePair().setValue(calcReturnRate(initPoint.getValue(), finalPoint.getValue())).setDate(finalPoint.getDate());
            rateSeries[initIdx] = returnRate;

            if (rollingReturnRateInfo.getMaxRateIdx() == -1
                    || rateSeries[rollingReturnRateInfo.getMaxRateIdx()].getValue().compareTo(returnRate.getValue()) < 0) {
                rollingReturnRateInfo.setMaxRateIdx(initIdx).setMaxRateStartDate(initPoint.getDate());
            }

            if (rollingReturnRateInfo.getMinRateIdx() == -1
                    || rateSeries[rollingReturnRateInfo.getMinRateIdx()].getValue().compareTo(returnRate.getValue()) > 0) {
                rollingReturnRateInfo.setMinRateIdx(initIdx).setMinRateStartDate(initPoint.getDate());
            }
        }

        rollingReturnRateInfo.setRateSeries(Arrays.stream(rateSeries).collect(Collectors.toList()));

        return Optional.of(rollingReturnRateInfo);
    }
}
